﻿using System;
using System.Collections;
using System.Collections.Generic;

namespace TradeHelp.MaterializedObjectViews.Views.Caches
{
    public sealed class ViewCache<T> : IViewCache<T>
    {
        private readonly Dictionary<T, CacheData> _cache = new Dictionary<T, CacheData>();

        public bool TryProcess(ViewMessage<T> viewMessage, out ViewMessage<T> retMessage)
        {
            retMessage = default(ViewMessage<T>);
            if (viewMessage.Action == ViewAction.Add && AddData(viewMessage.Data))
            {
                retMessage = viewMessage;
                return true;
            }
            if (viewMessage.Action == ViewAction.Remove)
            {
                T oldData;
                if(TryRemoveData(viewMessage.Data, out oldData))
                {
                    retMessage = ViewMessage<T>.CreateRemove(oldData);
                    return true;
                }
                return false;
            }
            if(viewMessage.Action == ViewAction.Update)
            {
                T oldData;
                T newData = viewMessage.Data;
                bool removed = TryRemoveData(viewMessage.PrevData, out oldData);
                bool added = AddData(newData);
                if(added && removed)
                {
                    var viewData = newData as IViewEquivalent<T>;
                    if(viewData != null && viewData.IsViewEquivalentTo(oldData))
                    {
                        return false;
                    }
                    retMessage = ViewMessage<T>.CreateUpdate(newData, oldData);
                    return true;
                }
                if(added)
                {
                    retMessage = ViewMessage<T>.CreateAdd(newData);
                    return true;
                }
                if (removed)
                {
                    retMessage = ViewMessage<T>.CreateRemove(oldData);
                    return true;
                }
                return false;
            }

            throw new NotImplementedException();
        }

        public void Clear()
        {
            _cache.Clear();
        }

        public IEnumerator<T> GetEnumerator()
        {
            foreach (var data in _cache)
            {
                yield return data.Value.Data;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        private bool AddData(T data)
        {
            CacheData cacheData;
            if (_cache.TryGetValue(data, out cacheData))
            {
                _cache[data] = new CacheData(data, cacheData.Count+1);
                return false;
            }
            _cache[data] = new CacheData(data, 1);
            return true;
        }

        private bool TryRemoveData(T data, out T oldData)
        {
            oldData = default(T);
            CacheData cacheData;
            if (_cache.TryGetValue(data, out cacheData))
            {
                if(cacheData.Count == 1)
                {
                    _cache.Remove(data);
                    oldData = cacheData.Data;
                    return true;
                }
                _cache[data] = new CacheData(data, cacheData.Count-1);
                return false;
            }
            throw new InvalidOperationException();
        }

        private struct CacheData
        {
            public CacheData(T data, int count)
            {
                Data = data;
                Count = count;
            }

            public readonly T Data;
            public readonly int Count;
        }
    }
}